package jflowmap.views.flowmap;


import java.awt.Color;
import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import jflowmap.data.ViewConfig;
import jflowmap.model.graph.Edge;
import jflowmap.model.graph.Node;
import jflowmap.model.ontology.Dimension;
import jflowmap.util.Colors;
import edu.umd.cs.piccolo.PNode;


public class PieChart implements Iterable<PieChart.PieSlice> {
    public static class PieSlice implements Comparable<PieSlice>, Anchorable {
        //private final VisualNode destination;
    	private final String destination;
        private final double radius;
        private final double startAngle;
        private final double arcAngle;
        private final double value;
        private final double total;
    	private final Color color;
    	private Point2D anchorPoint;
    	    	
        private PieSlice (/*VisualNode destination*/String destination, Color color,
        				 double startAngle, double value, double total, double radius) {
            this.destination = destination;
            this.startAngle = startAngle;
            this.value = value;
            this.arcAngle = value * 360 / total;
            this.radius = radius;
            //if (destination != null)
            	//this.color = Colors.getColorFor(destination, "flowmap");
            //else
            	//this.color = Color.GRAY;
            this.color = color;
            this.total = total;
        }
        
        public final long getPerMille () {
        	return Math.round((value/total)*1000); 
        }
        
        private final double getAnchorPointX () {
            return Math.sin(Math.toRadians(getMidAngle()+90))*radius/1.3;
        }
        
        private final double getAnchorPointY () {
            return Math.cos(Math.toRadians(getMidAngle()+90))*radius/1.3;
        }        

        final public double getStartAngle() {
            return startAngle;
        }

        final public double getArcAngle() {
            return arcAngle;
        }
        
        final public double getEndAngle() {
            return startAngle+arcAngle;
        }
        
        final public double getValue() {
            return value;
        }
        
        final public String getDestination() {
            return destination;
        }
        
        final public double getMidAngle() {
            return startAngle+arcAngle/2.0D;
        }
            
        @Override
        final public int compareTo(PieChart.PieSlice o) {
            return (int) (value - o.value);
        }
        
        @Override
        final public boolean equals (Object other) {
            if (!(other instanceof PieSlice)) return false;
            PieSlice o = (PieSlice) other;
            return this.destination.equals(o.destination) && this.value == o.value;
        }
        
        @Override
        final public int hashCode () {
            return (int) (1000*value);
        }

        @Override
        public Point2D getAnchorOn(PNode node) {
            if (anchorPoint == null) {
                synchronized(this) {
                    if (anchorPoint == null) {
                        Point2D tmp = new Point2D.Double(getAnchorPointX()+node.getX(), getAnchorPointY()+node.getY());
                        this.anchorPoint = tmp;
                    }
                }
            }
            return anchorPoint;
        }

		public Color getColor() {
			return this.color;
		}
    }
    
    private final List<PieSlice> slices;
    private final double total;
    private final double radius;
    
    private PieChart (final List<PieSlice> slices, double total, double radius) {
        if (slices.size() == 0) {
        	total = 20;
        	radius = 20;
        	slices.add(new PieSlice(null, Color.GRAY, 0, 2, 2, 5));
        } 
        
        this.slices = slices;
        this.total = total;
        this.radius = radius;
    }
    
    @Override
    public boolean equals (Object other) {
        if (!(other instanceof PieChart)) {
            return false;
        }
        PieChart pc = (PieChart) other;
        
        return this.slices.equals(pc.slices);
    }
    
    @Override
    public int hashCode () {
        if (slices.size() == 0)
            return 17;
        else return
            slices.iterator().next().hashCode();
    }
    
    public final int size() {
        return slices.size();
    }
    public final double getRadius() {
        return radius;
    }
    public final double getTotal () {
        return total;
    }
        
    public final Iterator<PieSlice> iterator () {
        return Collections.unmodifiableList(slices).iterator();
    }
    
    public static PieChart of (VisualNode parent, Collection<Edge> edgeCollection, double radius, ViewConfig config) {
    	final Dimension flowWeightDimension = parent.getVisualFlowMap().getCurrentValueDimension();
    	final List<Edge> edges = Edge.sortByValues(edgeCollection, flowWeightDimension);
        final List<PieSlice> slices = new Vector<PieSlice>();
        
        double total = 0.0D;
        int count = 0;
        for (Edge edge: edges) {
            final double weight = edge.getValue(flowWeightDimension).getValue();
            if (!Double.isNaN(weight)) {
                total += weight;
                count++;
            }
        }
        
        final int minSlices = config.getIntOrElse(ViewConfig.PROP_VIEW_PIE_MIN_SLICES, 6);
        //final double thresholdOther = config.getDoubleOrElse(ViewConfig.PROP_VIEW_PIE_OTHER_THRESHOLD, 0.9);
        final double thresholdSlice = config.getDoubleOrElse(ViewConfig.PROP_VIEW_PIE_SLICE_THRESHOLD, 0.04);
        
        double firstValue = Double.NaN;
        double curValue = 0.0D;
        int counter = 0;
        for (Edge edge: edges) {
            double value = edge.getValue(flowWeightDimension).getValue();
            
            if (Double.isNaN(value))
                continue;
            
            if (firstValue == Double.NaN)
            	firstValue = value;
                       
            final double startAngle = curValue * 360 / total;
            
            if (counter > minSlices && value < total*thresholdSlice && curValue > total*thresholdSlice) {
            	slices.add(new PieSlice("Andere (zusammengefasst)", Color.GRAY, startAngle, total-curValue, total, radius));
            	return new PieChart(slices, total, radius);
            } else {
            	if (counter == count-1)
            		value = total - curValue;
            		
            	curValue += value;
            	 
            	final Node oppositeNode = edge.getOppositeNode(parent.getNode());
            	
            	slices.add(new PieSlice(oppositeNode.getName(),
            				Colors.getColorFor(oppositeNode.getName(), "node"),
            				startAngle, value, total, radius));
            	Colors.setLabelFor("node", oppositeNode.getName(),
            						oppositeNode.getName());
            	counter++;
            }
        }
        
        return new PieChart(slices, total, radius);
    }
}
